﻿using System;
using System.Linq;
using System.Windows.Input;
using FluentValidation;
using LINQPad.Extensibility.DataContext;
using LINQPad.OpenAccess.Helpers;
using LINQPad.OpenAccess.Settings;
using LINQPad.OpenAccess.UI.Commands;
using LINQPad.OpenAccess.UI.Services;

namespace LINQPad.OpenAccess.UI.ViewModel
{
    /// <summary>
    /// Abstract base class for connection settings view models.
    /// </summary>
    public abstract class ConnectionSettingsViewModel<TViewModel> : ValidatableViewModelBase<TViewModel>, IConnectionSettingsViewModel
        where TViewModel : ValidatableViewModelBase
    {
        #region Private

        #region - Vars

        private DelegateCommand<object> __refreshCommand, __testConnectionCommand;
        private ConnectionSettingsHostViewModel _host;
        private bool _isActive;
        private string _providerName;
        private DriverSettings _driverSettings;
        private IConnectionInfo _connectionInfo;
        private IUserInteractionService _userInteractionService;

        #endregion

        #endregion

        #region Protected

        #region - Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionSettingsViewModel" /> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="description">The description.</param>
        /// <param name="userInteractionService">The user interaction service.</param>
        /// <param name="validator">The validator.</param>
        protected ConnectionSettingsViewModel( string name, string description, IUserInteractionService userInteractionService, IValidator<TViewModel> validator = null )
            : base( validator )
        {
            Guard.ArgumentNotNullOrEmpty( name, "name" );
            Guard.ArgumentNotNull( userInteractionService, "userInteractionService" );

            this.Name = name;
            this.Description = description ?? String.Empty;
            this._userInteractionService = userInteractionService;
        }

        #endregion

        #region - Functions

        /// <summary>
        /// Determines whether this instance can be refreshed.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this instance can refreshed; otherwise, <c>false</c>.
        /// </returns>
        protected virtual bool CanRefresh( )
        {
            return this.Owner != null && this._connectionInfo != null && this._driverSettings != null;
        }
        /// <summary>
        /// Determines whether this instance is currently able to test the connection.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this instance is currently able to test the connection; otherwise, <c>false</c>.
        /// </returns>
        protected virtual bool CanTestConnection( )
        {
            return this._connectionInfo != null
                    && this._connectionInfo != null
                    && this.IsValid;
        }
        /// <summary>
        /// Called to actually load the view model's settings.
        /// </summary>
        protected abstract void DoLoadSettings( );
        /// <summary>
        /// Called to actually write the view model's settings.
        /// </summary>
        protected abstract void DoWriteSettings( );
        /// <summary>
        /// Called to actually test the connection.
        /// <para>Should throw a corresponding exception if the test fails.</para>
        /// </summary>
        /// <param name="connectionInfo">The connection info.</param>
        /// <param name="driverSettings">The driver settings.</param>
        /// <exception cref="System.InvalidOperationException">No database information available !</exception>
        protected virtual void DoTestConnection( IConnectionInfo connectionInfo, DriverSettings driverSettings )
        {
            if ( connectionInfo.DatabaseInfo == null )
                throw new InvalidOperationException( "No database information available !" );
            using ( var connection = connectionInfo.DatabaseInfo.GetConnection( ) )
                connection.Open( );
        }
        /// <summary>
        /// Called when activated.
        /// </summary>
        protected virtual void OnActivated( )
        {

        }
        /// <summary>
        /// Called when deactivated.
        /// </summary>
        protected virtual void OnDeactivated( )
        {

        }
        /// <summary>
        /// Called when the connection host has changed.
        /// </summary>
        /// <param name="newHost">The new host.</param>
        /// <param name="oldHost">The old host.</param>
        protected virtual void OnHostChanged( ConnectionSettingsHostViewModel newHost, ConnectionSettingsHostViewModel oldHost )
        {
            if ( this.CanRefresh( ) )
                this.Refresh( );
            this.RaiseCanExecuteChangedForCommands( );
        }
        /// <summary>
        /// Raises the <see cref="E:IsActiveChanged" /> event.
        /// </summary>
        protected virtual void OnIsActiveChanged( )
        {
            var handler = this.IsActiveChanged;
            if ( handler != null )
                handler( this, EventArgs.Empty );
        }
        /// <summary>
        /// Raises the 'CanExecuteChanged' event for the view model's commands.
        /// </summary>
        protected virtual void RaiseCanExecuteChangedForCommands( )
        {
            if ( this.__refreshCommand != null )
                this.__refreshCommand.RaiseCanExecuteChanged( );
            if ( this.__testConnectionCommand != null )
                this.__testConnectionCommand.RaiseCanExecuteChanged( );
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets the connection info.
        /// </summary>
        /// <value>
        /// The connection info.
        /// </value>
        protected IConnectionInfo ConnectionInfo
        {
            get
            {
                return this._connectionInfo;
            }
        }
        /// <summary>
        /// Gets the database info.
        /// </summary>
        /// <value>
        /// The database info.
        /// </value>
        protected IDatabaseInfo DatabaseInfo
        {
            get
            {
                if ( this._connectionInfo == null )
                    return null;
                return this._connectionInfo.DatabaseInfo;
            }
        }
        /// <summary>
        /// Gets the driver settings.
        /// </summary>
        /// <value>
        /// The driver settings.
        /// </value>
        protected DriverSettings DriverSettings
        {
            get
            {
                return this._driverSettings;
            }
        }

        #endregion

        #endregion

        #region ValidatableViewModelBase<TViewModel> Members

        /// <summary>
        /// Invalidates this instance.
        /// </summary>
        public override void Invalidate( )
        {
            base.Invalidate( );

            this.RaiseCanExecuteChangedForCommands( );
            var host = this.Host;
            if ( host != null )
                host.Invalidate( );
        }
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected override void Dispose( bool disposing )
        {
            if ( disposing )
            {
                this.DisposeField( ref this.__refreshCommand );
                this.DisposeField( ref this.__testConnectionCommand );
                this._userInteractionService = null;
            }
            base.Dispose( disposing );
        }
        #endregion

        #region IActiveAware Members

        #region - Events

        /// <summary>
        /// Occurs when the 'Active' state of the instance has changed.
        /// </summary>
        public event EventHandler IsActiveChanged;

        #endregion

        #region - Properties

        /// <summary>
        /// Gets or sets a value indicating whether connection setting is active.
        /// </summary>
        /// <value>
        ///   <c>true</c> if the connection setting is active; otherwise, <c>false</c>.
        /// </value>
        public bool IsActive
        {
            get
            {
                return this._isActive;
            }
            set
            {
                if ( this._isActive != value )
                {
                    this._isActive = value;
                    this.OnPropertyChanged( ( ) => this.IsActive );
                    if ( value )
                        this.OnActivated( );
                    else
                        this.OnDeactivated( );
                    this.OnIsActiveChanged( );
                }
            }
        }

        #endregion

        #endregion

        #region IConnectionSettingsViewModel Members

        #region - Functions

        /// <summary>
        /// Determines whether the specified provider is supported.
        /// </summary>
        /// <param name="providerName">Name of the provider.</param>
        /// <returns>
        ///   <c>true</c> if the specified provider is supported; otherwise, <c>false</c>.
        /// </returns>
        public abstract bool IsProviderSupported( string providerName );
        /// <summary>
        /// Refreshes this instance.
        /// </summary>
        public abstract void Refresh( );
        /// <summary>
        /// Tests the connection.
        /// </summary>
        /// <param name="exception">The exception.</param>
        /// <returns></returns>
        public bool TestConnection( )
        {
            if ( !this.CanTestConnection( ) )
                return false;

            try
            {
                this.DoWriteSettings( );
                this.DoTestConnection( this._connectionInfo, this._driverSettings );
                if ( this._userInteractionService != null )
                    this._userInteractionService.ShowConnectionTestSucceededNotification( );
            }
            catch ( Exception ex )
            {
                if ( this._userInteractionService != null )
                    this._userInteractionService.ShowConnectionTestFailedNotification( ex );
                return false;
            }

            return true;
        }

        #endregion

        #region - Commands

        /// <summary>
        /// Gets the refresh command.
        /// </summary>
        /// <value>
        /// The refresh command.
        /// </value>
        public ICommand RefreshCommand
        {
            get
            {
                this.ThrowIfDisposed( );
                return this.__refreshCommand
                            ?? ( this.__refreshCommand = new DelegateCommand<object>
                                        (
                                            o => this.Refresh( ),
                                            o => this.CanRefresh( )
                                        )
                               );
            }
        }
        /// <summary>
        /// Gets the test connection command.
        /// </summary>
        /// <value>
        /// The test connection command.
        /// </value>
        public ICommand TestConnectionCommand
        {
            get
            {
                return this.__testConnectionCommand ?? ( this.__testConnectionCommand = new DelegateCommand<object>( _ => this.TestConnection( ), _ => this.CanTestConnection( ) ) );
            }
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets the description.
        /// </summary>
        /// <value>
        /// The description.
        /// </value>
        public string Description { get; private set; }
        /// <summary>
        /// Gets or sets the host.
        /// </summary>
        /// <value>
        /// The host.
        /// </value>
        public ConnectionSettingsHostViewModel Host
        {
            get
            {
                return this._host;
            }
            set
            {
                if ( this._host != value )
                {
                    var oldHost = this._host;
                    this._host = value;

                    this.OnHostChanged( value, oldHost );
                    this.OnPropertyChanged( ( ) => this.Host );
                    this.OnPropertyChanged( ( ) => this.Owner );
                }
            }
        }
        /// <summary>
        /// Gets the name.
        /// </summary>
        /// <value>
        /// The name.
        /// </value>
        public string Name { get; private set; }
        /// <summary>
        /// Gets the owner.
        /// </summary>
        /// <value>
        /// The owner.
        /// </value>
        public DriverSettingsViewModel Owner
        {
            get
            {
                if ( this._host == null )
                    return null;
                return this._host.Owner;
            }
        }
        /// <summary>
        /// Gets the name of the provider.
        /// </summary>
        /// <value>
        /// The name of the provider.
        /// </value>
        public string ProviderName
        {
            get
            {
                return this._providerName;
            }
            set
            {
                if ( this._providerName != value )
                {
                    this._providerName = value;
                    this.OnPropertyChanged( ( ) => this.ProviderName );
                }
            }
        }

        #endregion

        #endregion

        #region IPersistableSettingsViewModel<DriverSettings> Members

        /// <summary>
        /// Tries to load the view model's settings from the specified sources.
        /// </summary>
        /// <param name="connectionInfo">The connection info.</param>
        /// <param name="source">The source.</param>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        public bool TryLoadSettingsFrom( IConnectionInfo connectionInfo, DriverSettings source )
        {
            this._connectionInfo = connectionInfo;
            this._driverSettings = source;

            if ( connectionInfo != null
                    && source.Connection != null
                    && String.Equals( source.Connection.Name, this.Name ) )
                this.DoLoadSettings( );

            this.RaiseCanExecuteChangedForCommands( );

            return true;
        }
        /// <summary>
        /// Writes the view model's settings to the specified targets.
        /// </summary>
        /// <param name="connectionInfo">The connection info.</param>
        /// <param name="target">The target.</param>
        public void WriteSettingsTo( IConnectionInfo connectionInfo, DriverSettings target )
        {
            Guard.ArgumentNotNull( connectionInfo, "connectionInfo" );
            Guard.ArgumentNotNull( target, "target" );

            this._connectionInfo = connectionInfo;
            this._driverSettings = target;
            this.DoWriteSettings( );
            if ( target.Connection == null )
                target.Connection = new ConnectionSettings { Name = this.Name };
            else
                target.Connection.Name = this.Name;
        }

        #endregion
    }
}
